{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "young-research",
   "metadata": {},
   "source": [
    "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n",
    "SPDX-License-Identifier: Apache-2.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "antique-adobe",
   "metadata": {},
   "source": [
    "# Visualization - Grouping and Appearance Customization\n",
    "\n",
    "In this notebook we'll examine how to use the `--group-by` feature of the `%%gremlin` magic to group vertices and how to customize the appearance of these groups. \n",
    "\n",
    "In addition, we will look at how to use the `--display-property`, `--edge-display-property`, and `--label-max-length` options to modify the appearances of node and edge labels.\n",
    "\n",
    "**Note** This notebook builds on the visualization patterns explained in [Air-Routes-Gremlin](Air-Routes-Gremlin.ipynb) notebook, so if you are not familiar with how the visualzation feaure works we recommend reviewing that prior to beginning this notebook.\n",
    "\n",
    "This notebook uses the air-routes dataset to demonstrate some of the different options available.  If your cluster does not already contain this data then run the cell below to load the data into your Neptune cluster otherwise you can skip the next cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "improving-resort",
   "metadata": {},
   "outputs": [],
   "source": [
    "%seed --model Property_Graph --language gremlin --dataset airports --run"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "changing-demonstration",
   "metadata": {},
   "source": [
    "With the air-routes data now loaded we are ready to begin looking at how to group and customize our result visualization.  \n",
    "\n",
    "## Query requirements for customizable visualizations\n",
    "\n",
    "The functionality of the `--display-property` and `--edge-display-property` options is dependent on accessing the values corresponding to the vertex and/or edge property name keys specified. As such, we must enable them to find the property key/value pairs required by ensuring that they are included in the query's results set.\n",
    "\n",
    "First, we need to ensure that our query is set up as a graph traversal. This must always start with the graph traversal source, `g.V()`. The traversal itself begins with the `has` step that retrieves our set of starting vertices, followed by the `outE` step, which finds all of the edges originating from those vertices. \n",
    "\n",
    "In addition, we will need an `elementMap` step to retrieve the edge properties in a map format that our visualizer can understand. `elementMap` representations of edges also return basic label and id informations about the vertices that the edges connect, so we do not need to explicitly return vertices at this time.\n",
    "\n",
    "Putting all of these steps together, we might get a query like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "legislative-anthony",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -de dist\n",
    "g.V().has('code','SAF').outE().elementMap()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "parallel-delay",
   "metadata": {},
   "source": [
    "If we click on the vertices and nodes and open the Details view (bulleted list icon in the top right of the visualization), we can see that `outE` and `elementMap` have retrieved full sets of properties, but only for the edges in the graph. If we want to retrieve the same for vertices, we need to also include an `inV` traversal step to retrieve the set of the vertices that the edges are directed into.\n",
    "\n",
    "The traversal is getting a little more complicated now, so we also need the `path` step to help keep track of the traversal's history. To ensure that we are getting the full property sets for all of the edges and vertices, `path` will be accompanied by a `by` modulator, which will post process the path elements per a specified return format.\n",
    "\n",
    "There are a few options for what we can put in the `by` modulator:\n",
    "- `path().by(valueMap(true))`\n",
    "- `path().by(valueMap().with(WithOptions.tokens))`\n",
    "- `path().by(elementMap())`\n",
    "\n",
    "When choosing between the `valueMap` and `elementMap` steps, note that the requirements to properly visualize queries will differ slightly.\n",
    "\n",
    "If using `valueMap(true)`, we will need to specify the `-p` query hint, followed by a comma-separated sequence of the same Gremlin traversal steps used in the query itself.\n",
    "\n",
    "Run the three cells below to observe the effects of `-p` being missing and present for a `valueMap` return, alongside some of our customization options."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "referenced-individual",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_node_labels = '{\"airport\":\"city\"}'\n",
    "my_edge_labels = '{\"route\":\"dist\"}'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "copyrighted-defensive",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -l 20 -g country -de $my_edge_labels\n",
    "g.V().has('code','LHR').outE().inV().path().by(valueMap(true)).limit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "alleged-clearing",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,oute,inv -d $my_node_labels -l 20 -g country -de $my_edge_labels\n",
    "g.V().has('code','LHR').outE().inV().path().by(valueMap(true)).limit(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fiscal-recruitment",
   "metadata": {},
   "source": [
    "If we elect to use `elementMap` instead, we can forgo the `-p` option; `elementMap` already returns enough information about edges for path patterns to be determined automatically. Run the query below, and observe that the visualization returned is the same the one with `-p` and `valueMap` above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "spanish-destiny",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -l 20 -g country -de $my_edge_labels\n",
    "g.V().has('code','LHR').outE().inV().path().by(elementMap()).limit(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "stuffed-charger",
   "metadata": {},
   "source": [
    "`elementMap()` is clearly simpler to use than `-p` and `valueMap(true)`, and should be utilized wherever possible. But the two are not always interchangeable, and often times `-p` is needed to specify path patterns where `elementMap()` is insufficient. The choice will always depend on the individual query's requirements."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "collect-violin",
   "metadata": {},
   "source": [
    "## Node Property Options\n",
    "\n",
    "Visualizing the results of a query will result in a graph where each vertex contains an identifying property. By default, the property used is generated automatically from the label property. If desired, it can instead abide by the property (or set of label properties) specified using the `--display-property` or `-d` parameter, followed by the property name. \n",
    "\n",
    "Additionally, labels are truncated after exceeding a default maximum length. This maximum length value can be modified by using the `--label-max-length` or `-l` parameter, followed by the desired length in characters.\n",
    "\n",
    "\n",
    "### Default Node Properties\n",
    "\n",
    "By default, the property used is the value of each vertex's label property. Run the query below to observe the default labeling for results set of all vertices connected to Cozumel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "chief-hebrew",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "outstanding-spice",
   "metadata": {},
   "source": [
    "The results show us only three distinct labels, corresponding to each of the label properties `airport`, `country`, and `continent`.\n",
    "\n",
    "In some cases, a label property may not present may not be present in some or all of the vertices returned in the results set (i.e. T.label key/value pairs have not been returned in the results). \n",
    "\n",
    "This will result in a concatenated list of all of the vertex's properties being displayed instead, as we can observe with the following query."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "committed-ordinance",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "documentary-sudan",
   "metadata": {},
   "source": [
    "### Specifying a Single Node Property for all Vertices\n",
    "\n",
    "There may also be cases where it is desired to show a specific vertex property as the label on each graph node. This can be done by using the `--display-property` or `-d` parameter within the `%%gremlin` line magic. The property name needs to be a case sensitive match for the name in the vertex.\n",
    "\n",
    "**Note** Finding the property name can be accomplished using the Details View and clicking on a vertex.  This includes the `T.id` and `T.label` properties.\n",
    "\n",
    "Let's run the following query to see the results of displaying the results set of all vertices connected to Cozumel, while also specifying that we want to display the `code` property on every vertex."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "settled-adjustment",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -d code\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "third-supervisor",
   "metadata": {},
   "source": [
    "Looking at the resulting visualized graphs, each individual node can now be identified by its distinct code."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "typical-harvest",
   "metadata": {},
   "source": [
    "\n",
    "### Using Different Node Properties for each Label\n",
    "\n",
    "Instead of displaying the values of a single property, we can also choose to specify different properties to display for each type of label. This feature can be useful if different labels in the graph have different property sets, or if you want only a subset of vertices under certain labels to have the displayed property modified.\n",
    "\n",
    "We will first need to define a JSON-format string variable in the following format, containing each label and its corresponding propreties to be displayed:\n",
    "\n",
    "`display_var = '{\"label_1\":\"property_1\",\"label_2\":\"property_2\"}'`\n",
    "\n",
    "Let's try using this to define different display properties for the `airport`, `country`, and `continent` labels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "southern-wrestling",
   "metadata": {},
   "outputs": [],
   "source": [
    "display_var = '{\"airport\":\"code\",\"country\":\"desc\",\"continent\":\"desc\"}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "exempt-memphis",
   "metadata": {},
   "source": [
    "Now, we can take the previous query and pass `display_var` into the displayed properties parameter via the notebooks line variable injection functionality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "wireless-retirement",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -d $display_var\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "sweet-paradise",
   "metadata": {},
   "source": [
    "In the resulting visualization, we can see that the `airport` vertices retain the `code` property values, while the `country` and `continent` nodes now display their more descriptive `desc` property values, as we desired.\n",
    "\n",
    "### Specifying the Maximum Label Length\n",
    "\n",
    "All node labels in the graph abide by a maximum length value, after which they are visually truncated. By default, this value is 10.\n",
    "\n",
    "You may encounter scenarios where you want to modify the truncation length to either be shorter or longer than the default value. This can be done by adding the `--label-max-length` or `-l` parameter to your Gremlin query, followed by the desired length in characters.\n",
    "\n",
    "Let's try to visualize a graph of all airports connected to Cozumel, while indicating that we want to display the full airport names on the nodes. Note that the label truncation length is left as default."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "infectious-palmer",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -d desc\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "champion-marks",
   "metadata": {},
   "source": [
    "We can see that most of the labels are cut off too early to be adequately descriptive!\n",
    "\n",
    "Let's try the same query again, this time incorporating the `-l` parameter, followed by a maximum length value of `60`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "unusual-cooperation",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -d desc -l 60\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "square-trunk",
   "metadata": {},
   "source": [
    "Now, we can see the full name of every airport on the graph.\n",
    "\n",
    "## Edge Property Options\n",
    "\n",
    "Some graph visualizations of Gremlin magic queries will contain edges that are also accompanied by a visual label. The property displayed on each edge defaults to the edge's label property. \n",
    "\n",
    "We can specify what property values to display on the visualized edges in a similar fashion to how we customized node labels in the previous section. This is done by using the `--edge-display-property` or `-de` parameter, followed by the property name, or a json formatted string containing label-value:property-name pairs. \n",
    "\n",
    "### Specifying a single property for all edge labels\n",
    "\n",
    "Using a single property name as the parameter value for `--edge-display-property` will result every edge label being changed to property. Again, note that you can find the properties available by using the details view and clicking on any edge in the graph visualization.\n",
    "\n",
    "Let's observe the results of appending `-de dist` to a query that retrieves all routes from Austin, USA to Wellington, NZ. Note that we use `by(valueMap(true))` in the query to ensure that we are retrieving all of the edge properties."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "textile-fundamentals",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,oute,inv,oute,inv,oute,inv -de dist\n",
    "g.V().has('airport','code','AUS').\n",
    "  repeat(outE().inV().simplePath()).\n",
    "  until(has('code','WLG')).\n",
    "  limit(5).\n",
    "  path().\n",
    "    by(valueMap(true)).\n",
    "    by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "found-commodity",
   "metadata": {},
   "source": [
    "In both graphs, we can see that every edge is now indicated by its outgoing vertex via the `outV` property.\n",
    "\n",
    "### Specifying edge label properties in JSON format\n",
    "\n",
    "Making use of the cell variable injection feature, we can also define a JSON-format string variable containing our desired label-value:property-name pairs, and pass it into `-de`. This follows the format used when injecting JSON variables into `-d` and `-g`:\n",
    "\n",
    "`display_edge_var = '{\"label_1\":\"property_1\",\"label_2\":\"property_2\"}'`\n",
    "\n",
    "Let's re-define the same label-value:property-name pair used in the last query, this time in the JSON string variable format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "stunning-control",
   "metadata": {},
   "outputs": [],
   "source": [
    "display_edge_var = '{\"route\":\"dist\"}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "internal-judgment",
   "metadata": {},
   "source": [
    "Then, we can pass `$display_edge_var` into `-de`, and observe that we get the same result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "public-zealand",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,oute,inv,oute,inv,oute,inv -de $display_edge_var\n",
    "g.V().has('airport','code','AUS').\n",
    "  repeat(outE().inV().simplePath()).\n",
    "  until(has('code','WLG')).\n",
    "  limit(5).\n",
    "  path().\n",
    "    by(valueMap(true)).\n",
    "    by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "equipped-alias",
   "metadata": {},
   "source": [
    "This is also an example of a query where we can interchange `elementMap()` for `valueMap(true)`, removing the need for `-p` path pattern hints."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "equal-relaxation",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -de $display_edge_var\n",
    "g.V().has('airport','code','AUS').\n",
    "  repeat(outE().inV().simplePath()).\n",
    "  until(has('code','WLG')).\n",
    "  limit(5).\n",
    "  path().\n",
    "    by(elementMap()).\n",
    "    by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "serial-casino",
   "metadata": {},
   "source": [
    "## Grouping Options\n",
    "\n",
    "When visualizing the results of a query the grouping of the vertices follows a couple of rules:\n",
    "\n",
    "* If no grouping property is specified and the label for a vertex is returned then the vertices are grouped by label.\n",
    "    * If the label does not exist in the results then all vertices are grouped into a single group\n",
    "* To group vertices by a specific property use the `--group-by` or `-g` switch on the `%%gremlin` line magic.  \n",
    "    * The name provided must be a case sensitive match to the property name of the vertex (i.e. `Name` will not group by the `name` property)\n",
    "    * If the name specified does not match any property of a vertex then that vertex is grouped in a default group\n",
    "* If you would like to not group by any property then the `--ignore-groups` flag will not group the vertices\n",
    "\n",
    "### Default Grouping\n",
    "\n",
    "Let's take a look at what our air-routes graph looks like if we run a query and use the default grouping. Try running the query below and clicking on the Graph tab to see all the vertices connected to Cozumel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "noticed-complaint",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V().has('airport','code','CZM').both().path()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "coordinated-correlation",
   "metadata": {},
   "source": [
    "From the results we see three groups each represented by a different color.  Each of the vertices is added to a group based on it's label.  \n",
    "\n",
    "Grouping also occurs when we use the `by()` modulators to specify additional return fields.  Running the query below to see how the items are grouped when we find all connected vertices for Cozumel and return all their properties and tokens (T.id and T.label)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "related-invitation",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V().has('code', 'ANC').both().path().by(valueMap().with(WithOptions.tokens))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "pending-complex",
   "metadata": {},
   "source": [
    "From the resultant visualization we see that we still have three groups of vertices, grouped by the label.  \n",
    "\n",
    "However, what would happen if we don't return the label in the results.  Run the query below to see how the items are grouped when we find all connected vertices for Cozumel and return just their properties, not the label."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "minute-gentleman",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V().has('code', 'ANC').both().path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "suburban-landing",
   "metadata": {},
   "source": [
    "From the results we see that all our vertices are in a single group now and colored the same.  When our results do not contain the label for the vertex we must then specify the property we want to group by.  \n",
    "\n",
    "### Specifying the property to group by\n",
    "\n",
    "To specify the property to group by we use either the `--group-by` or `-g` switch followed by the property name.  The property name needs to be a case sensitive match for the name in the vertex.  \n",
    "\n",
    "**Note** Finding the property name can be accomplished using the Details View and clicking on a vertex.  This includes the `T.id` and `T.label` properties.\n",
    "\n",
    "Let's run the query below and see what our grouping looks like if we find all routes from Cozumel and group them by their `country`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "august-tyler",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g country\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "subjective-challenge",
   "metadata": {},
   "source": [
    "As we see our results are now split into three groups (MX/US/CA) based on the country property.  \n",
    "\n",
    "Let's run the query below and see what our grouping looks like if we find all connections to Cozumel and group them by their `country`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "handy-victoria",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g country\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "preceding-sword",
   "metadata": {},
   "source": [
    "The resultant graph looks very similar to the previous one except that their is now four groups instead of three.  Unlike in the previous example where we only returned `route` connections, this query returned all connected vertices so our resultset came back with a `continent` and `country` vertex in addition to the `airport` vertices.  Neither the `country` nor `continent` vertices have a `country` property.  When a vertex does not contain the property specified to group by then that vertex is put into a default group.  This same behavior occurs when you use the incorrect casing for the property name, as shown in the query below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "sorted-essay",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g Country\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "tight-mauritius",
   "metadata": {},
   "source": [
    "### Grouping on different properties for each label"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "black-bailey",
   "metadata": {},
   "source": [
    "While the `continent` and `country` vertices cannot be grouped on properties exclusive to `airport` vertices, we do have the ability to add additional properties with which to form new groups. To do this, we will make use of Jupyter's built-in line magic variable injection to pass in a variable containing a JSON-format string, where we can specify what property we want to use to group each of the individual vertex labels.  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fossil-asian",
   "metadata": {},
   "source": [
    "The group-by variable must be defined in following format:  \n",
    "\n",
    "`groups_var = '{\"label_1\":\"property_1\",\"label_2\":\"property_2\"}'`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "excess-update",
   "metadata": {},
   "source": [
    "Let's try defining group-by with individual properties for `airport`, `continent`, and `country`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "applicable-pride",
   "metadata": {},
   "outputs": [],
   "source": [
    "groups_var = '{\"airport\":\"country\",\"country\":\"desc\",\"continent\":\"code\"}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "stone-patent",
   "metadata": {},
   "source": [
    "Then, we can rerun our previous query, this time with `$groups_var`, and see that the `continent` and `country` vertices now belong to their own groups based on the specified properties.  \n",
    "\n",
    "**Note** we also need to use `valueMap(true)` so that the label names and relevant properties can be accessed for group matching."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "funky-contrary",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g $groups_var\n",
    "g.V().hasLabel('airport').has('code','CZM').both().path().by(valueMap(true))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bored-issue",
   "metadata": {},
   "source": [
    "### Ignoring Grouping\n",
    "\n",
    "In certain situations you may want your visualization to contain no groups.  This can be accomplished by adding the `--ignore-groups` flag to your query as shown by running the next cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "present-serum",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv --ignore-groups\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "mental-knowing",
   "metadata": {},
   "source": [
    "Now that we know how to group our vertices together let's take a look at how to customize the appearance of these groups, and other aspects of the graph.\n",
    "\n",
    "## Appearance Customization\n",
    "\n",
    "The Amazon Neptune Notebooks use an open source library called [Vis.js](https://github.com/visjs) to assist with drawing the graph diagrams. Vis.js provides a rich set of customizable settings. The documentation for most of the visualization settings used in this notebook can be found [here](https://visjs.org/) and in particular the graph network drawing documentation can be found [here](https://visjs.github.io/vis-network/docs/network/).  \n",
    "\n",
    "To see the current settings used by your notebook you can use the `%graph_notebook_vis_options` line magic command. Try running the cell below.  \n",
    "\n",
    "To change any of these settings create a new cell and use `%%graph_notebook_vis_options` to change them (note the two percent signs indicating a cell magic).\n",
    "\n",
    "To customize the appearance of node groups, we want to use the [groups](https://visjs.github.io/vis-network/docs/network/groups.html#) options. There is a nearly endless amount of customization you can make to the groups using the options provided, but we will demonstrate some of the most common ones in the next few sections.\n",
    "\n",
    "<div class=\"alert alert-block alert-info\"> \n",
    "<details>\n",
    "    <summary><b><i>Note on valueMap vs elementMap for group keys</i></b></summary>\n",
    "    \n",
    "The exact value of the group keys that you use may vary, depending on the format specified inside the `by()` step.\n",
    "    \n",
    "For the queries in the sections that follow, note that `by(valueMap())` is used. `valueMap` will return the property keys in the path in list form, for example:\n",
    "    \n",
    "```\n",
    "path[{'country': ['MX'], 'code': ['CZM'], 'longest': [10165], ...\n",
    "```\n",
    "\\\n",
    "To match, the group keys in `%%graph_notebook_vis_options` must also follow the list format.\n",
    "    \n",
    "```\n",
    "{\n",
    "  \"groups\": {\n",
    "      \"['CA']\": {\"color\": \"red\"},\n",
    "      \"['MX']\": {\"color\": \"rgba(9, 104, 178, 1)\"},      \n",
    "      ...\n",
    "  }\n",
    "}\n",
    "```\n",
    "\\\n",
    "On the other hand, we may elect to use `elementMap` instead of `valueMap` in the `by` modulator. `elementMap` differs in that it always returns property keys alone, as their original type. It will also return the `label` and `id` token properties automatically; for `valueMap`, tokens have to be explicitly requests. \n",
    "    \n",
    "For example, the same path generated with `elementMap` would look look like:\n",
    "\n",
    "```\n",
    "path[{<T.id: 1>: '365', <T.label: 4>: 'airport', 'code': 'CZM', 'country': 'MX', ...\n",
    "```\n",
    "\\\n",
    "Subsequently, we need to specify the group keys in `%%graph_notebook_vis_options` only as the base string value.\n",
    "    \n",
    "```\n",
    "{\n",
    "  \"groups\": {\n",
    "      \"CA\": {\"color\": \"red\"},\n",
    "      \"MX\": {\"color\": \"rgba(9, 104, 178, 1)\"},      \n",
    "      ...\n",
    "  }\n",
    "}\n",
    "```\n",
    "\\\n",
    "For more information on usage of `valueMap` and `elementMap`, please refer to Kelvin Lawrence's Practical Gremlin tutorials below.\n",
    "\n",
    "https://kelvinlawrence.net/book/Gremlin-Graph-Guide.html#vm\n",
    "\\\n",
    "https://kelvinlawrence.net/book/Gremlin-Graph-Guide.html#element-map\n",
    "\n",
    "</details>\n",
    "</div>\n",
    "\n",
    "\n",
    "### Specifying Group Colors\n",
    "\n",
    "Specifying the colors of groups is probably one of the most common customizations performed.  To accomplish this we specify the options using the `%%graph_notebook_vis_options` magic as shown below. \n",
    "\n",
    "For each of the associated group names, we use the exact property value followed by the options you would like to use for that group. Finding the exact property value for the group name can be accomplished by looking at the data returned in the Console tab.\n",
    "\n",
    "Run the next two cells to set the colors for our three groups to red for the airports in Canada, green for the airports in the US, and blue for the airports in Mexico.  In the case of color, the values can be specified by name, RGBA value, or Hex value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "every-classification",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%graph_notebook_vis_options\n",
    "{\n",
    "  \"groups\": {\n",
    "      \"['CA']\": {\"color\": \"red\"},\n",
    "      \"['MX']\": {\"color\": \"rgba(9, 104, 178, 1)\"},      \n",
    "      \"['US']\": {\"color\": \"#00FF00\"}\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "sharing-frame",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g country\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "tough-vatican",
   "metadata": {},
   "source": [
    "### Specifying Group Shapes\n",
    "\n",
    "In addition to specifying the color of the groups you are also able to customize the shape of the vertex using one of the following options. \n",
    "\n",
    "The types with the label inside of it are: `ellipse`, `circle`, `database`, `box`, `text`.\n",
    "\n",
    "The ones with the label outside of it are: `diamond`, `dot`, `star`, `triangle`, `triangleDown`, `hexagon`, and `square`\n",
    "\n",
    "Run the cells below to see what our visualization looks like with shapes specified."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "modified-interstate",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%graph_notebook_vis_options\n",
    "{\n",
    "  \"groups\": {\n",
    "      \"['CA']\": {\"color\": \"red\", \"shape\": \"box\"},\n",
    "      \"['MX']\": {\"color\": \"rgba(9, 104, 178, 1)\" , \"shape\": \"oval\"},      \n",
    "      \"['US']\": {\"color\": \"#00FF00\", \"shape\": \"star\"}\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "pleased-attribute",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g country\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "essential-jewel",
   "metadata": {},
   "source": [
    "### Specifying Group Icons and Images\n",
    "\n",
    "In addition to specifying shapes icons and images can also be specified to represent our groups.  \n",
    "\n",
    "Icons can either be from `Ionicons` or `FontAwesome` version 4 or 5.  Icons are specified by setting the `shape` to `icon` and then specifying the `code` for the icon.\n",
    "\n",
    "<div class=\"alert alert-block alert-info\"> \n",
    "<details>\n",
    "    <summary><b><i>Warning on FontAwesome versions</i></b></summary>\n",
    "    \n",
    "Please note that the `%%graph_notebook_vis_options` examples in the sample notebooks are configured for FontAwesome 4, as supported by Jupyter Classic Notebook 6. On the other hand, JupyterLab 3 and Notebook 7 use FontAwesome 5, where FontAwesome 4 settings have been deprecated.\n",
    "    \n",
    "To make the icons work correctly on FontAwesome 5, we need to switch the `icon.face` attribute value to `'Font Awesome 5 Free'`. The icon weight also needs to be set explicitly, using either `\"weight\": \"bold\"` or `\"font-weight\": \"900\"`. As an example, the icon settings for the `['US']` group in the `%%graph_notebook_vis_options` below should be changed to:\n",
    "\n",
    "```\n",
    "\"['US']\": {\n",
    "    \"shape\": \"icon\",\n",
    "    \"icon\": {\n",
    "        \"face\": \"'Font Awesome 5 Free'\", # Needs nested single quotes\n",
    "        \"weight\": \"bold\",\n",
    "        \"code\": \"\\uf072\",\n",
    "        \"color\": \"#00FF00\"\n",
    "    }\n",
    "}\n",
    "```\n",
    "\n",
    "</details>\n",
    "</div>\n",
    "\n",
    "\n",
    "Images must be either local to the notebook or publically available on the internet.  Images are specified by setting the `shape` to `image` or `circularImage` and then setting the `image` property to the address of the image to display.\n",
    "\n",
    "Running the two cells below will display the Mexico group using an image of the Mexican flag and the US group using an airplane icon from FontAwesome."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "powerful-envelope",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "%%graph_notebook_vis_options\n",
    "{\n",
    "  \"groups\": {\n",
    "    \"['CA']\": {\n",
    "      \"color\": \"red\"\n",
    "    },\n",
    "    \"['MX']\": {\n",
    "        \"shape\": \"image\", \n",
    "        \"image\": \"https://cdn.countryflags.com/thumbs/mexico/flag-round-250.png\"\n",
    "    },\n",
    "    \"['US']\": {\n",
    "      \"shape\": \"icon\",\n",
    "      \"icon\": {\n",
    "        \"face\": \"FontAwesome\",\n",
    "        \"code\": \"\\uf072\",\n",
    "        \"color\": \"#00FF00\"\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "handy-holocaust",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g country\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "transsexual-judgment",
   "metadata": {},
   "source": [
    "### Specifying Group Sizes\n",
    "\n",
    "We can also specify a custom value for a group's node size. This is accomplished by setting the `size` property of the group.\n",
    "\n",
    "**Note** Only shapes that do not have the label inside them are impacted by this property."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "moral-bulletin",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%graph_notebook_vis_options\n",
    "{\n",
    "  \"groups\": {\n",
    "    \"['CA']\": {\n",
    "        \"color\": \"red\", \n",
    "        \"size\": 3\n",
    "    },\n",
    "    \"['MX']\": {\n",
    "        \"shape\": \"image\", \n",
    "        \"image\": \"https://cdn.countryflags.com/thumbs/mexico/flag-round-250.png\", \n",
    "        \"size\": 50\n",
    "    },\n",
    "    \"['US']\": {\n",
    "      \"shape\": \"icon\",\n",
    "      \"icon\": {\n",
    "        \"face\": \"FontAwesome\",\n",
    "        \"code\": \"\\uf072\",\n",
    "        \"color\": \"#00FF00\"\n",
    "      }, \n",
    "      \"size\": 37\n",
    "    }\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "tough-maine",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -p v,inv -g country\n",
    "g.V().hasLabel('airport').has('code','CZM').out('route').path().by(valueMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "familiar-radical",
   "metadata": {},
   "source": [
    "### Specifying Parallel Same-Direction Edge Behavior\n",
    "\n",
    "There is one more customization option, which allows for parallel, same-direction edges to either be drawn on top of each other (default), or spaced apart.\n",
    "\n",
    "Let's start by running the following cell to create such a scenario in the air-routes graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5258c6ba",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "g.addV(\"airport\").property(id,\"10000\").property(\"type\",\"airport\").property(\"code\",\"FOO\").property(\"country\", \"BAR\").\n",
    "  addE(\"route\").property(id,\"1\").from(V(\"365\")).to(V(\"10000\")).property(\"dist\",1000).\n",
    "  addE(\"route\").property(id,\"2\").from(V(\"365\")).to(V(\"10000\")).property(\"dist\",2000).\n",
    "  addE(\"route\").property(id,\"3\").from(V(\"365\")).to(V(\"10000\")).property(\"dist\",3000)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23326296",
   "metadata": {},
   "source": [
    "Now, run the next cell to visualize the new node and edges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d32681c0",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "%%gremlin -g country -d code -de dist\n",
    "g.V().hasLabel('airport').has('code','CZM').outE().inV().has('code','FOO').path().by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9ee0a6d",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Notice that three different edges are returned in the results, but the visualized graph only appears to have one edge.\n",
    "\n",
    "This is a result of the default value `straightCross` used for the `edges`->`smooth`->`type` vis setting. All of the parallel edges technically exist in the graph visualization, but the `straightCross` setting draws the edges on top of each other, obscuring all but one.\n",
    "\n",
    "To fix this, we can set the edge smoothing type to `dynamic`. Run the two cells below to observe the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dc482a8",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "%%graph_notebook_vis_options\n",
    "{\n",
    "  \"edges\": {\n",
    "    \"color\": {\n",
    "      \"inherit\": false\n",
    "    },\n",
    "    \"smooth\": {\n",
    "      \"enabled\": true,\n",
    "      \"type\": \"dynamic\"\n",
    "    },\n",
    "    \"arrows\": {\n",
    "      \"to\": {\n",
    "        \"enabled\": true,\n",
    "        \"type\": \"arrow\"\n",
    "      }\n",
    "    },\n",
    "    \"font\": {\n",
    "      \"face\": \"courier new\"\n",
    "    }\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4549242",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "%%gremlin -g country -d code -de dist\n",
    "g.V().hasLabel('airport').has('code','CZM').outE().inV().has('code','FOO').path().by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4336f687",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "With the `dynamic` setting, all of the edges are now spaced out and clearly visible.\n",
    "\n",
    "Lastly, clean up the dummy data we inserted for this example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce11925d",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "g.V().hasId('10000').drop()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79cfb095",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "What we have demonstrated in this notebook is only a small set of the options and combinations available for customizing the appearance of groups within the notebook.  Please refer to the [Vis.js groups](https://visjs.github.io/vis-network/docs/network/groups.html#) documentation for a complete list of the options available."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
